From cc7f2f81cc59751fcc689731dcd60af5da5723ba Mon Sep 17 00:00:00 2001
From: Xi Ruoyao <xry111@xry111.site>
Date: Mon, 13 Mar 2023 16:23:37 +0800
Subject: [PATCH] gstrfuncs: Improve inline version of g_strdup() to avoid
 breaking C++ code

Wrap the logic into a G_ALWAYS_INLINE function, instead of using a
complex statement-expression which is not allowed in braced initializer
lists and expanded into some bad thing when it's used as
`::g_strdup(...)`.

We cannot use `__builtin_constant_p (str)` because GCC documentation
clearly states that it always produces 0 when str is a const char *
argument of an inline function.  But `__builtin_constant_p (!str)`,
`__builtin_constant_p (!!str)`, and
`__builtin_constant_p (strlen (str))` functions properly with `-O1` or
above enabled.

Fixes #2936.
---
 glib/gstrfuncs.h   | 43 ++++++++++++++++++++++++++-----------------
 glib/tests/cxx.cpp | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 17 deletions(-)

diff --git a/glib/gstrfuncs.h b/glib/gstrfuncs.h
index 8a6830294f..cb021b658d 100644
--- a/glib/gstrfuncs.h
+++ b/glib/gstrfuncs.h
@@ -204,23 +204,6 @@ gboolean             (g_str_has_prefix) (const gchar *str,
     (g_str_has_suffix) (STR, SUFFIX)                                          \
   )
 
-#define g_strdup(STR)                                                         \
-  (__builtin_constant_p ((STR)) ?                                             \
-    (G_LIKELY ((STR) != NULL) ?                                               \
-      G_GNUC_EXTENSION ({                                                     \
-        const char *const ___str = ((STR));                                   \
-        const char *const __str = _G_STR_NONNULL (___str);                    \
-        const size_t __str_len = strlen (__str) + 1;                          \
-        char *__dup_str = (char *) g_malloc (__str_len);                      \
-        (char *) memcpy (__dup_str, __str, __str_len);                        \
-      })                                                                      \
-      :                                                                       \
-      (char *) (NULL)                                                         \
-    )                                                                         \
-    :                                                                         \
-    (g_strdup) ((STR))                                                        \
-  )
-
 #endif /* !defined (__GI_SCANNER__) */
 #endif /* !defined (__GTK_DOC_IGNORE__) */
 #endif /* G_GNUC_CHECK_VERSION (2, 0) */
@@ -318,6 +301,32 @@ GLIB_AVAILABLE_IN_ALL
 gchar*                g_strjoin	       (const gchar  *separator,
 					...) G_GNUC_MALLOC G_GNUC_NULL_TERMINATED;
 
+#if G_GNUC_CHECK_VERSION(2, 0)
+#ifndef __GTK_DOC_IGNORE__
+#ifndef __GI_SCANNER__
+
+G_ALWAYS_INLINE static inline char *
+g_strdup_inline (const char *str)
+{
+  if (__builtin_constant_p (!str) && !str)
+    return NULL;
+
+  if (__builtin_constant_p (!!str) && !!str && __builtin_constant_p (strlen (str)))
+    {
+      const size_t len = strlen (str) + 1;
+      char *dup_str = (char *) g_malloc (len);
+      return (char *) memcpy (dup_str, str, len);
+    }
+
+  return g_strdup (str);
+}
+
+#define g_strdup(x) g_strdup_inline (x)
+
+#endif /* !defined (__GI_SCANNER__) */
+#endif /* !defined (__GTK_DOC_IGNORE__) */
+#endif /* G_GNUC_CHECK_VERSION (2, 0) */
+
 /* Make a copy of a string interpreting C string -style escape
  * sequences. Inverse of g_strescape. The recognized sequences are \b
  * \f \n \r \t \\ \" and the octal format.
diff --git a/glib/tests/cxx.cpp b/glib/tests/cxx.cpp
index 2431340092..bc7967ccee 100644
--- a/glib/tests/cxx.cpp
+++ b/glib/tests/cxx.cpp
@@ -349,6 +349,36 @@ test_strdup_macro (void)
   g_free (str);
 }
 
+static void
+test_strdup_macro_qualified (void)
+{
+  gchar *str;
+
+  g_assert_null (::g_strdup (NULL));
+
+  str = ::g_strdup ("C++ is cool too!");
+  g_assert_nonnull (str);
+  g_assert_cmpstr (str, ==, "C++ is cool too!");
+  g_free (str);
+}
+
+static void
+test_strdup_macro_nested_initializer (void)
+{
+  struct
+  {
+    char *p, *q;
+  } strings = {
+    g_strdup (NULL),
+    g_strdup ("C++ is cool too!"),
+  };
+
+  g_assert_null (strings.p);
+  g_assert_nonnull (strings.q);
+  g_assert_cmpstr (strings.q, ==, "C++ is cool too!");
+  g_free (strings.q);
+}
+
 static void
 test_str_has_prefix (void)
 {
@@ -527,6 +557,8 @@ main (int argc, char *argv[])
   g_test_add_func ("/C++/str-equal", test_str_equal);
   g_test_add_func ("/C++/strdup", test_strdup);
   g_test_add_func ("/C++/strdup/macro", test_strdup_macro);
+  g_test_add_func ("/C++/strdup/macro/qualified", test_strdup_macro_qualified);
+  g_test_add_func ("/C++/strdup/macro/nested-initializer", test_strdup_macro_nested_initializer);
   g_test_add_func ("/C++/str-has-prefix", test_str_has_prefix);
   g_test_add_func ("/C++/str-has-prefix/macro", test_str_has_prefix_macro);
   g_test_add_func ("/C++/str-has-suffix", test_str_has_suffix);
-- 
GitLab

